package com.example.netty.io.core

import io.netty.buffer.UnpooledHeapByteBuf
import io.netty.channel.ChannelHandlerAdapter
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelPromise
import io.netty.handler.timeout.IdleState
import io.netty.handler.timeout.IdleStateEvent
import java.net.InetSocketAddress

class DataHandlerAdapter(
    private val tag: String,
    private val messageListener: IMessageListener
) : ChannelHandlerAdapter() {

    private var channelContext: ChannelHandlerContext? = null

    override fun channelActive(ctx: ChannelHandlerContext) {
        this.channelContext = ctx
        val address = ctx.channel().remoteAddress() as InetSocketAddress
        val ip = address.address.hostAddress
        NettyLog.d(
            tag,
            "channelActive: thread = ${Thread.currentThread().name} ip=$ip"
        )
        messageListener.onEventArrived(ctx.channel(), EventType.CONNECTED)
    }

    override fun channelInactive(ctx: ChannelHandlerContext) {
        this.channelContext = ctx
        NettyLog.d(tag, "channelInactive ${Thread.currentThread().name}")
    }

    override fun channelUnregistered(ctx: ChannelHandlerContext) {
        super.channelUnregistered(ctx)
        NettyLog.d(tag, "channelUnregistered ${Thread.currentThread().name}")
        messageListener.onEventArrived(ctx.channel(), EventType.DISCONNECTED)
    }

    override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable?) {
        this.channelContext = ctx
        messageListener.onEventArrived(ctx.channel(), EventType.Error(cause))
        NettyLog.e(tag, "exceptionCaught", cause)
    }

    override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
        this.channelContext = ctx
        val message = if (msg is UnpooledHeapByteBuf) {
            msg.toString(NettyConfigure.charset)
        } else {
            msg.toString()
        }
        NettyLog.i(tag, "channelRead : message= $message")
        messageListener.onMessageArrived(ctx.channel(), message = message)
    }

    override fun write(ctx: ChannelHandlerContext, msg: Any?, promise: ChannelPromise?) {
        this.channelContext = ctx
        val log = when (msg) {
            is UnpooledHeapByteBuf -> {
                msg.toString(NettyConfigure.charset)
            }

            is ByteArray -> {
                String(msg, NettyConfigure.charset)
            }

            else -> {
                msg?.toString()
            }
        }
//        NettyLog.i(tag, "write:msg = $log")
        ctx.write(msg)
    }

    override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any?) {
        this.channelContext = ctx
        when (evt) {
            is IdleStateEvent -> {
                onIdleEvent(evt.state())
            }

            else -> {
                super.userEventTriggered(ctx, evt)
            }
        }
    }

    private fun onIdleEvent(state: IdleState) {
        NettyLog.t(tag, "onIdleEvent : $state")
        when (state) {
            IdleState.WRITER_IDLE -> {
                val heartbeat = messageListener.getHeartbeat()
                if (heartbeat.isNotEmpty()) {
                    sendData(messageListener.getHeartbeat())
                }
            }

            else -> {}
        }
    }

    private fun sendData(data: ByteArray): Boolean {
        return channelContext?.let { ctx ->
            val byteBuf = ctx.alloc().buffer()
            byteBuf.writeBytes(data)
            val future = ctx.channel().writeAndFlush(byteBuf)
            future.isSuccess
        } ?: false
    }
}